/* 
**  Created by wplaat
**
**  Copyright (C) 2008 
**  ==================
**
**  This program is free software; you can redistribute it and/or modify
**  it under the terms of the GNU General Public License as published by
**  the Free Software Foundation, version 2.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
**
**  History:
**   20-11-2008  Start programming
**   30-11-2008  Added google analysic http request
**   07-12-2008  Add user_var parameter in google analysic http request
**   30-12-2008  Added store highscore on internet.
**   06-01-2009  Google analysic function improved.
**   15-01-2009  Add http chunked frame support.
**   21-01-2009  Create one unique cookie during startup.
**   26-01-2009  Increase internal buffersize to 8kB.
*/

#include <stdio.h>
#include <ctype.h>
#include <gccore.h>
#include <ogcsys.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h> 
#include <malloc.h>
#include <network.h>
#include <ogc/lwp_watchdog.h>
#include <sys/types.h>
#include <sys/errno.h>
#include <fcntl.h>

#include "httplib.h"

// -----------------------------------------------------------
// DEFINES
// -----------------------------------------------------------

//#define DEBUG(X) 

#define TCP_CONNECT_TIMEOUT     5000
#define TCP_BLOCK_SIZE          (16 * 1024)
#define TCP_BLOCK_RECV_TIMEOUT  4000
#define TCP_BLOCK_SEND_TIMEOUT  4000
#define HTTP_TIMEOUT            300000
#define MAX_LEN        			256
#define NUM_THREADS             1
#define MAX_BUFFER_SIZE		    8192

// -----------------------------------------------------------
// VARIABLES
// -----------------------------------------------------------

lwp_t threads[NUM_THREADS];
mutex_t mutexcheck;
mutex_t mutexversion;
bool do_tcp_treat;

char *appl_host; 
char *appl_path;

char appl_new_version[MAX_LEN];
char appl_release_notes[MAX_BUFFER_SIZE];
char appl_token[MAX_LEN];
char appl_highscore[MAX_BUFFER_SIZE];

char appl_name[MAX_LEN];
char appl_version[MAX_LEN];

char appl_id1[MAX_LEN];
char appl_url1[MAX_LEN];

char appl_id2[MAX_LEN];
char appl_url2[MAX_LEN];
char appl_userData2[MAX_LEN];

char appl_id3[MAX_LEN];
char appl_url3[MAX_LEN];
char appl_userData3[MAX_LEN];

char var_cookie[MAX_LEN];

extern GXRModeObj *rmode;
int  tcp_state;
int  tcp_state_prev;

char *http_host;
u16 http_port;
char *http_path;
u32 http_max_size;

http_res result;
u32 http_status;
u32 content_length;
u8 *http_data;
u8 *temphttp_data;

int retval;
u32 http_status;
u32 outlen;
u8  *outbuf;
char *report;
FILE *file_ptrs;
const struct block emptyblock = {0, NULL};
bool deter = FALSE;

struct block downloadfile(const char *url)
{
	u8 *outbuf;
	u32 outlen, http_status;
	int retval;
	Debug("request");
	retval = http_request(url, 1 << 31);
	while(retval != TRUE)
	{
	switch(retval) {
	case HTTPR_ERR_MOVED:
		Debug("redirect");
		retval = http_request(report, 1 << 31);
		break;
	case FALSE:
		Debug("FAILED");
		return emptyblock;
		break;
	}
	}
		
	http_get_result(&http_status, &outbuf, &outlen); 
	struct block b;
	b.data = outbuf;
	b.size = outlen;
	return b;
}


// -----------------------------------------------------------
// TCP METHODES
// -----------------------------------------------------------

s32 tcp_socket(void) 
{
        //DEBUG( printf("tcp_socket:enter\n"); )
        s32 s;

        s = net_socket (AF_INET, SOCK_STREAM, IPPROTO_IP);
        if (s < 0) 
		{
//			DEBUG ( printf("net_socket failed: %d\n",s); )
        }

	    //DEBUG ( printf("tcp_socket: leave [%d]\n",s); )
        return s;
}

s32 tcp_connect(char *host, const u16 port)
{
	    //DEBUG ( printf("tcp_connect:enter [host=%s|port=%d]\n",host,port); )
		
        struct hostent *hp;
        struct sockaddr_in sa;
        s32 s, res;
        s64 t;

        hp = net_gethostbyname(host);
        if (!hp || !(hp->h_addrtype == PF_INET)) 
		{            
//			DEBUG ( printf("net_gethostbyname failed: %d\n",errno); )
            return errno;
        }

        s = tcp_socket();
        if (s < 0)
		{
//		   DEBUG ( printf("Error creating socket, exiting"); )
           return s;
	    }

        memset (&sa, 0, sizeof (struct sockaddr_in));
        //sa.sin_family= PF_INET;
		sa.sin_family= AF_INET;
        sa.sin_len = sizeof (struct sockaddr_in);
        sa.sin_port= htons(port);
        memcpy ((char *) &sa.sin_addr, hp->h_addr_list[0], hp->h_length);

//        DEBUG ( printf("DNS resolve=%d.%d.%d.%d\n", (unsigned char) hp->h_addr_list[0][0], (unsigned char) hp->h_addr_list[0][1], (unsigned char) hp->h_addr_list[0][2], (unsigned char)  hp->h_addr_list[0][3]); )

        t = gettime();
        while (true) 
		{
                if (ticks_to_millisecs (diff_ticks (t, gettime ())) > TCP_CONNECT_TIMEOUT) 
				{
//					DEBUG ( printf("tcp_connect timeout\n"); )
                    net_close (s);
                    return -ETIMEDOUT;
                }

                res = net_connect (s, (struct sockaddr *) &sa, sizeof (struct sockaddr_in));

                if (res < 0) 
				{
                   if (res == -EISCONN)  break;

                   if (res == -EINPROGRESS || res == -EALREADY) 
				   {
                       usleep (20 * 1000);
                       continue;
                   }

//   				   DEBUG ( printf("net_connect failed: %d\n",res); )
                   net_close(s);
                   return res;
                }
                break;
        }
		//DEBUG ( printf("tcp_connect:leave\n"); )
        return s;
}

char * tcp_readln(const s32 s, const u16 max_length, const u64 start_time, const u16 timeout) 
{		
        char *buf;
        u16 c;
        s32 res;
        char *ret;

        buf = (char *) malloc (max_length);

        c = 0;
        ret = NULL;
        while (true) 
		{
                if (ticks_to_millisecs (diff_ticks (start_time, gettime ())) > timeout)
                        break;

                res = net_read (s, &buf[c], 1);

                if ((res == 0) || (res == -EAGAIN)) 
				{
                        usleep (20 * 1000);
                        continue;
                }

                if (res < 0) 
				{                       
//						DEBUG ( printf("tcp_readln failed: %d\n", res); )
                        break;
                }

                if ((c > 0) && (buf[c - 1] == '\r') && (buf[c] == '\n')) 
				{
                        if (c == 1) 
						{
                                ret = strdup ("");
                                break;
                        }
                        ret = strndup (buf, c - 1);
                        break;
                }
                c++;
                if (c == max_length) break;
        }
        free (buf);
		return ret;
}

bool tcp_read(const s32 s, u8 **buffer, const u32 length) 
{
	    //DEBUG ( printf("tcp_read:enter\n"); )
		
        u8 *p;
        u32 step, left, block, received;
        s64 t;
        s32 res;

        step = 0;
        p = *buffer;
        left = length;
        received = 0;

        t = gettime ();
        while (left) 
		{
                if (ticks_to_millisecs (diff_ticks (t, gettime ())) > TCP_BLOCK_RECV_TIMEOUT)
			    {
//				   DEBUG ( printf("tcp_read timeout\n"); )
                   break;
                }

                block = left;
                if (block > 2048) block = 2048;
				
                res = net_read (s, p, block);
                if ((res == 0) || (res == -EAGAIN)) 
				{
                    usleep (20 * 1000);
                    continue;
                }

                if (res < 0) 
				{
//					DEBUG ( printf("net_read failed %d\n", res); )
                    break;
                }

                received += res;
                left -= res;
                p += res;

                if ((received / TCP_BLOCK_SIZE) > step) 
				{
                    t = gettime ();
                    step++;
                }
        }

		//DEBUG ( printf("tcp_read:leave\n"); )
        return left == 0;
}

bool tcp_write(const s32 s, const u8 *buffer, const u32 length)
{
	    //DEBUG ( printf("tcp_write: enter\n"); )
		
        const u8 *p;
        u32 step, left, block, sent;
        s64 t;
        s32 res;

        step = 0;
        p = buffer;
        left = length;
        sent = 0;

        t = gettime ();
        while (left) 
		{
                if (ticks_to_millisecs (diff_ticks (t, gettime ())) > TCP_BLOCK_SEND_TIMEOUT) 
				{
// 					DEBUG ( printf("tcp_write: timeout\n"); )
                    break;
                }

                block = left;
                if (block > 2048) block = 2048;

                res = net_write (s, p, block);
                if ((res == 0) || (res == -56))
				{
                    usleep (20 * 1000);
                    continue;
                }

                if (res < 0) 
				{
//					DEBUG ( printf("tcp_write: failed:%d\n",res); )
                    break;
                }
                sent += res;
                left -= res;
                p += res;

                if ((sent / TCP_BLOCK_SIZE) > step)
				{
                    t = gettime ();
                    step++;
                }
        }
        return left == 0;
		//DEBUG ( printf("tcp_write: leave\n"); )
}


// -----------------------------------------------------------
// HTTP METHODES
// -----------------------------------------------------------

bool http_split_url(char **host, char **path, const char *url)
{
		//DEBUG ( printf("http_split_url: enter\n"); )
		
        const char *p;
        char *c;

        if (strncasecmp (url, "http://", 7))
		{
//		   DEBUG ( printf("http_split_url: leave [false]\n"); )
           return false;
		}

        p = url + 7;
        c = strchr (p, '/');

        if (c[0] == 0)
		{
//		    DEBUG ( printf("http_split_url: leave [false]\n"); )
		    return false;
	    }

        *host = strndup (p, c - p);
        *path = strdup (c);

		//DEBUG ( printf("http_split_url: leave [true]\n"); )		
        return true;
}
	
bool http_request(const char *url, const u32 max_size)
{
	    //DEBUG ( printf("http_request: enter\n"); )
 		Debug("begin request");
        int linecount;
		boolean chunked=false;
		int emptycount=0;
		Debug("split");
        if (!http_split_url(&http_host, &http_path, url)) return false;

        http_port = 80;
        http_max_size = max_size;
       
        http_status = 404;
        content_length = 0;
        http_data = NULL;
		Debug("connect");
        int s = tcp_connect(http_host, http_port);

//		DEBUG ( printf("tcp_connect(%s, %hu) = %d\n",http_host, http_port, s); )
        if (s < 0) 
		{
            result = HTTPR_ERR_CONNECT;
            return false;
        }
		
        char *request = (char *) malloc(2048);
        char *r = request;
        Debug("sprintf");
		r += sprintf (r,"GET %s HTTP/1.1\r\n", http_path); 
        r += sprintf (r,"Host: %s\r\n", http_host);
		r += sprintf (r,"User-Agent: %s/%s (compatible; v3.X; Nintendo Wii)\r\n", appl_name, appl_version);  
        r += sprintf (r,"Accept: text/xml,text/html,text/plain,image/gif\r\n");
        r += sprintf (r,"Cache-Control: no-cache\r\n\r\n");

//        DEBUG ( printf("request = %s", request); )
		Debug("write");
        bool b = tcp_write (s, (u8 *) request, strlen (request));

        free (request);
        linecount = 0;
		Debug("for");
        for (linecount=0; linecount < 32; linecount++) 
		{
			Debug("read line");
            char *line = tcp_readln ((s32) s, (u16) 0xff, (u64) gettime(), (u16) HTTP_TIMEOUT);
//		    DEBUG ( printf("tcp_readln: %s\n", line ? line:"(null)"); )
			Debug(line);
            if (!line) 
			{
                http_status = 404;
                result = HTTPR_ERR_REQUEST;
                break;
            }

            if (chunked && (emptycount==1))
			{			   
			   sscanf (line, "%x", &content_length);	
			   Debug("chunksize");
			   // Protect against crash frame
			   if (content_length>5000) content_length=0;
			   
			   free (line);
               line = NULL;			 
               break;		
			}
			
            if (strlen(line)<1) 
			{
			    if (chunked) 
				{
				    if (++emptycount>1) 
					{
					  free (line);
					  line = NULL;
                      break;
					}
					//DEBUG ( printf("Empty line\n"); )
				}	
                else
                {				
                  free (line);
                  line = NULL;
                  break;
			    }
            }
			Debug("sscanf");
            sscanf (line, "HTTP/1.%*u %u", &http_status);
            sscanf (line, "Content-Length: %u", &content_length);
			if ( strstr(line, "Transfer-Encoding: chunked")!=0 )
			{
				//DEBUG ( printf("Chunked frame found\n"); )
				free (line);
				line = NULL;
				chunked=true;
            }
						
            free (line);
            line = NULL;
        }
        
//		DEBUG ( printf("content_length = %d, status = %d, linecount = %d\n", content_length, http_status, linecount); )

        if (http_status != 200) 
		{
            result = HTTPR_ERR_STATUS;
            net_close (s);
            return false;
        }
      	
	    if (linecount == 32 || !content_length) http_status = 404;
				
        if (content_length > http_max_size) 
		{
           result = HTTPR_ERR_TOOBIG;
           net_close (s);
           return false;
        }
        http_data = (u8 *) malloc (content_length);
        b = tcp_read (s, &http_data, content_length);
		char * line = tcp_readln ((s32) s, (u16) 0xff, (u64) gettime(), (u16) HTTP_TIMEOUT);
		Debug("test");
		Debug(line);
		free (line); //Due to exactly cutting the chunk the CRLF is leftover and so this was a lazy way to get around that, i could just increase the content length by 2
		line = tcp_readln ((s32) s, (u16) 0xff, (u64) gettime(), (u16) HTTP_TIMEOUT);
		Debug(line);
		unsigned int *temp;
        sscanf (line, "%x", temp);
		b = tcp_read (s, &temphttp_data, *temp);
		*http_data += *temphttp_data;
		if (!b) 
		{
           free (http_data);
           http_data = NULL;
           result = HTTPR_ERR_RECEIVE;
           net_close (s);
           return false;
        }
        		
        result = HTTPR_OK;
        net_close(s);

		//DEBUG ( printf("http_request: leave\n"); )
        return true;
}

extern bool http_get_result(u32 *_http_status, u8 **content, u32 *length) 
{
		//DEBUG ( printf("http_get_result: enter\n"); )
		
        if (http_status) *_http_status = http_status;

        if (result == HTTPR_OK) 
		{
           *content = http_data;
           *length = content_length;
        } 
		else 
		{
           *content = NULL;
           *length = 0;
        }

        free (http_host);
        free (http_path);

		//DEBUG ( printf("http_get_result: leave\n"); )
        return true;
}

void Debug(char * msg)
{
	if(!deter)
	{
	char * split = "\n";
    file_ptrs = fopen("fat://applog.txt", "a");
    fwrite(msg,1,strlen(msg), file_ptrs);
	fwrite(split,1,strlen(split), file_ptrs);
	fclose (file_ptrs);
	}
}

// -----------------------------------------------------------
// THE END
// -----------------------------------------------------------

